package edu.gatech.ealf;

import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Point;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;

import edu.gatech.ealf.config.EALFConfigPanel;
import edu.gatech.ealf.ettplaf.ExtendedToolTipLookAndFeel;
import edu.gatech.ealf.keyboardassistanceplaf.KeyboardAssistanceLookAndFeel;
import edu.gatech.ealf.magiclenseplaf.MagicLenseLookAndFeel;
import edu.gatech.ealf.secretservantplaf.SecretServantLookAndFeel;
import edu.gatech.ealf.ttsplaf.TextToSpeechLookAndFeel;

/**
 * The Easy Accessibility Look And Feel (EALF)
 * To use, just call the static initialize method with your config filename. 
 * NOTE: DO NOT REGISTER THIS WITH THE UI MANAGER AS A LookAndFeel because it does not extend Look and Feel.
 * 
 * @author Justin
 * 
 */
public class EasyAccessibilityLookAndFeel {

	/**
	 * The map of component configuration information.
	 * The Application Developer should NOT use this
	 */
    public static Map<String, ComponentAccessibilityConfiguration> componentConfig = new HashMap<String, ComponentAccessibilityConfiguration>(
            200);

    private static boolean enabled = false;

    private static Document doc = null;

    private static String configFilename;

    private static JPanel ealfConfigPanel = null;

    private static boolean startEnabledEALF = true;

    private static boolean startEnabledMagicLense = true;

    private static boolean startEnabledTTS = true;

    private static boolean startEnabledETT = true;
    
    private static boolean startEnabledKA = true;

    /**
     * Initialize must be called with the EALF config filename. This will parse
     * the config and add the auxiliary Look and Feels.
     * This MUST BE CALLED BY application developer.
     * @param configFilename the file name or path to the XML configuration file
     */
    public static void initialize(String configFilename) {
        EasyAccessibilityLookAndFeel.configFilename = configFilename;
        try {
            if (configFilename == null || configFilename.equals("")) {
            	EasyAccessibilityLookAndFeel.configFilename = "EALFConfig.xml";
            	
            }
            try{
            	loadConfig(EasyAccessibilityLookAndFeel.configFilename);
            } catch (Exception e){
            	System.err.println("Couldn't Load EasyAccessibilityLookAndFeel configuration from file: " + EasyAccessibilityLookAndFeel.configFilename + " because: " + e.getMessage());
            }
            if (UIManager.getSystemLookAndFeelClassName().equals(
                    "apple.laf.AquaLookAndFeel"))
                UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");

            UIManager.addAuxiliaryLookAndFeel(SecretServantLookAndFeel
                    .getInstance());

            installInitial();

        } catch (ClassNotFoundException ex) {
            Logger.getLogger(EasyAccessibilityLookAndFeel.class.getName()).log(
                    Level.SEVERE, null, ex);
        } catch (InstantiationException ex) {
            Logger.getLogger(EasyAccessibilityLookAndFeel.class.getName()).log(
                    Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            Logger.getLogger(EasyAccessibilityLookAndFeel.class.getName()).log(
                    Level.SEVERE, null, ex);
        } catch (UnsupportedLookAndFeelException ex) {
            Logger.getLogger(EasyAccessibilityLookAndFeel.class.getName()).log(
                    Level.SEVERE, null, ex);
        }
    }

    private static void installInitial() {
        if (startEnabledEALF) {
            enabled = true;
            if (startEnabledETT) {
                ExtendedToolTipLookAndFeel.getInstance().install();
            }
            if (startEnabledTTS) {
                TextToSpeechLookAndFeel.getInstance().install();
            }
            if (startEnabledMagicLense) {
                MagicLenseLookAndFeel.getInstance().install();
            }
            if (startEnabledKA) {
                KeyboardAssistanceLookAndFeel.getInstance().install();
            }
        }

    }
    /**
     * Disables the EasyAccessibilityLookAndFeel and all of its sub look and feels.
     * This shouldn't need to be called by application developer because it is called from Configuration Panel.
     * However, it can be called by the application developer.
     */
    public static void uninstall() {
        if (enabled) {
            enabled = false;
            TextToSpeechLookAndFeel.getInstance().uninstall();
            MagicLenseLookAndFeel.getInstance().uninstall();
            ExtendedToolTipLookAndFeel.getInstance().uninstall();
            KeyboardAssistanceLookAndFeel.getInstance().uninstall();
        }
    }

    /**
     * Enables the EasyAccessibilityLookAndFeel and all of its sub look and feels.
     * This shouldn't need to be called by application developer because it is called from Configuration Panel.
     * However, it can be called by the application developer.
     */
    public static void install() {
        if (!enabled) {
            enabled = true;
            TextToSpeechLookAndFeel.getInstance().install();
            MagicLenseLookAndFeel.getInstance().install();
            ExtendedToolTipLookAndFeel.getInstance().install();
            KeyboardAssistanceLookAndFeel.getInstance().install();
        }
    }

    /**
     * Returns a JPanel that is the EALF configuration panel
     * @return The EALF Configuration JPanel
     */
    public static JPanel getConfigurationPanel() {
        ealfConfigPanel = new EALFConfigPanel();
        return ealfConfigPanel;
    }

    private static Document parseDocument(String configFileName)
            throws JDOMException, IOException {
        SAXBuilder builder = new SAXBuilder();
        builder.setIgnoringElementContentWhitespace(true);
        doc = builder.build(new File(configFileName));

        return doc;
    }

    private static void loadConfig(String configFilename) throws IOException, JDOMException {
        Document doc = null;
        doc = parseDocument(configFilename);

        Element root = doc.getRootElement();

        /*
         * Settings
         */
        Element settings = root.getChild("settings");
        if (settings != null) {
            Element easyAccessibilityLookAndFeel = settings
                    .getChild("EasyAccessibilityLookAndFeel");
                if(easyAccessibilityLookAndFeel != null){
                String en = easyAccessibilityLookAndFeel
                        .getAttributeValue("enabled");
                if (en != null && en.equals("false")) {
                    startEnabledEALF = false;
                }
            }
            Element extendedToolTipLookAndFeel = settings
                    .getChild("ExtendedToolTipLookAndFeel");
            if (extendedToolTipLookAndFeel != null) {
                String en = extendedToolTipLookAndFeel.getAttributeValue("enabled");
                if (en != null && en.equals("false")) {
                    startEnabledETT = false;
                }
                String foregroundColor = extendedToolTipLookAndFeel
                        .getChildText("foregroundColor");
                String backgroundColor = extendedToolTipLookAndFeel
                        .getChildText("backgroundColor");
                String toolTipDelayMs = extendedToolTipLookAndFeel
                        .getChildText("toolTipDelayMs");
                String extendedToolTipDelayMs = extendedToolTipLookAndFeel
                        .getChildText("extendedToolTipDelayMs");
                int fgColorNumber = -1;
                int bgColorNumber = -1;
                if (foregroundColor.startsWith("0x")) {
                    foregroundColor = foregroundColor.replaceFirst("0x", "");
                    foregroundColor = foregroundColor.toUpperCase();
                    if (foregroundColor.length() > 6) {
                        foregroundColor = foregroundColor.substring(
                                foregroundColor.length() - 6, 8);
                    }
                    try {
                        fgColorNumber = Integer.parseInt(foregroundColor, 16);
                    } catch (NumberFormatException e) {
                        System.err
                                .println("ERROR: Setting: ExtendedToolTipLookAndFeel/foregroundColor is an invalid number. Please use either a hex number (e.g., 0xAB12DE) or a base 10 integer between 0 and 2^24 (e.g., 125)");
                    }
                } else {
                    try {
                        fgColorNumber = Integer.parseInt(foregroundColor);
                    } catch (NumberFormatException e) {
                        System.err
                                .println("ERROR: Setting: ExtendedToolTipLookAndFeel/foregroundColor is an invalid number. Please use either a hex number (e.g., 0xAB12DE) or a base 10 integer between 0 and 2^24 (e.g., 125)");
                    }
                }
                if (backgroundColor.startsWith("0x")) {
                    backgroundColor = backgroundColor.replaceFirst("0x", "");
                    backgroundColor = backgroundColor.toUpperCase();
                    if (backgroundColor.length() > 6) {
                        backgroundColor = backgroundColor.substring(
                                backgroundColor.length() - 6, 8);
                    }
                    try {
                        bgColorNumber = Integer.parseInt(backgroundColor, 16);
                    } catch (NumberFormatException e) {
                        System.err
                                .println("ERROR: Setting: ExtendedToolTipLookAndFeel/backgroundColor is an invalid number. Please use either a hex number (e.g., 0xAB12DE) or a base 10 integer between 0 and 2^24 (e.g., 125)");
                        System.err.println(backgroundColor);
                    }
                } else {
                    try {
                        bgColorNumber = Integer.parseInt(backgroundColor);
                    } catch (NumberFormatException e) {
                        System.err
                                .println("ERROR: Setting: ExtendedToolTipLookAndFeel/backgroundColor is an invalid number. Please use either a hex number (e.g., 0xAB12DE) or a base 10 integer between 0 and 2^24 (e.g., 125)");
                        System.err.println(backgroundColor);
                    }
                }
                if (fgColorNumber >= 0) {
                    ExtendedToolTipLookAndFeel.foregroundColor = new Color(
                            fgColorNumber);
                }
                if (bgColorNumber >= 0) {
                    ExtendedToolTipLookAndFeel.backgroundColor = new Color(
                            bgColorNumber);
                }
                ExtendedToolTipLookAndFeel.toolTipDelayMs = Integer
                        .parseInt(toolTipDelayMs);
                ExtendedToolTipLookAndFeel.extendedToolTipDelayMs = Integer
                        .parseInt(extendedToolTipDelayMs);
            }

            Element magicLenseLookAndFeel = settings
                    .getChild("MagicLenseLookAndFeel");
            if (magicLenseLookAndFeel != null) {
                String en = magicLenseLookAndFeel.getAttributeValue("enabled");
                if (en != null && en.equals("false")) {
                    startEnabledMagicLense = false;
                }
                MagicLenseLookAndFeel.getInstance().setZoomFactor(Double
                        .parseDouble(magicLenseLookAndFeel
                                .getChildText("zoomFactor")));
            }

            Element textToSpeechLookAndFeel = settings
                    .getChild("TextToSpeechLookAndFeel");
            if (textToSpeechLookAndFeel != null) {
                String en = textToSpeechLookAndFeel.getAttributeValue("enabled");
                if (en != null && en.equals("false")) {
                    startEnabledTTS = false;
                }
                String voice = textToSpeechLookAndFeel.getChildText("voice");
                if (voice.equals(TextToSpeechLookAndFeel.ALAN)) {
                    TextToSpeechLookAndFeel.getInstance().setVoiceString(
                            TextToSpeechLookAndFeel.ALAN);
                } else if (voice.equals(TextToSpeechLookAndFeel.KEVIN)) {
                    TextToSpeechLookAndFeel.getInstance().setVoiceString(
                            TextToSpeechLookAndFeel.KEVIN);
                } else if (voice.equals(TextToSpeechLookAndFeel.KEVIN16)) {
                    TextToSpeechLookAndFeel.getInstance().setVoiceString(
                            TextToSpeechLookAndFeel.KEVIN16);
                }
            }
        }
        /*
         * Components
         */
        Element components = root.getChild("components");
        List componentList = components.getChildren("component");
        for (Object o : componentList) {
            Element compEl = (Element) o;
            String name = compEl.getAttributeValue("name");
            String toolTipText = compEl.getChildText("toolTipText");
            String extendedToolTipText = compEl
                    .getChildText("extendedToolTipText");
            String audioOutputText = compEl.getChildText("audioOutputText");
            String audioOutputFilename = compEl
                    .getChildText("audioOutputFilename");
            String audioActionText = compEl.getChildText("audioActionText");
            String audioActionFilename = compEl
                    .getChildText("audioActionFilename");
            ComponentAccessibilityConfiguration compConfig = new ComponentAccessibilityConfiguration(
                    name, toolTipText, extendedToolTipText, audioOutputText,
                    audioOutputFilename, audioActionText, audioActionFilename);
            componentConfig.put(name, compConfig);
        }

    }

    /**
     * Saves the configuration information based on the current values.
     * This shouldn't need to be called by application developer because it is called by the Configuration Panel.
     * But it may be called in order to invoke an explicit save.
     */
    public static void saveConfig() {
    	if (doc == null) {
    		doc = new Document();
    		Element accessibilityEl = new Element("accessibility");
    		doc.setRootElement(accessibilityEl);
    	}
        Element root = doc.getRootElement();

        /*
         * Settings
         */
        Element settings = getOrCreateElement(root, "settings");
        Element easyAccessibilityLookAndFeel = getOrCreateElement(settings,
                "EasyAccessibilityLookAndFeel");
        easyAccessibilityLookAndFeel
                .setAttribute("enabled",
                        ((Boolean) EasyAccessibilityLookAndFeel.isEnabled())
                                .toString());

        Element extendedToolTipLookAndFeel = getOrCreateElement(settings,
                "ExtendedToolTipLookAndFeel");
        extendedToolTipLookAndFeel
                .setAttribute("enabled", ((Boolean) ExtendedToolTipLookAndFeel
                        .getInstance().isEnabled()).toString());
        Element foregroundColorEl = getOrCreateElement(
                extendedToolTipLookAndFeel, "foregroundColor");
        Element backgroundColorEl = getOrCreateElement(
                extendedToolTipLookAndFeel, "backgroundColor");
        Element toolTipDelayMsEl = getOrCreateElement(
                extendedToolTipLookAndFeel, "toolTipDelayMs");
        Element extendedToolTipDelayMsEl = getOrCreateElement(
                extendedToolTipLookAndFeel, "extendedToolTipDelayMs");

        foregroundColorEl.setText("0x"
                + Integer
                        .toHexString(ExtendedToolTipLookAndFeel.foregroundColor
                                .getRGB()));
        backgroundColorEl.setText("0x"
                + Integer
                        .toHexString(ExtendedToolTipLookAndFeel.backgroundColor
                                .getRGB()));
        toolTipDelayMsEl
                .setText(((Integer) ExtendedToolTipLookAndFeel.toolTipDelayMs)
                        .toString());
        extendedToolTipDelayMsEl
                .setText(((Integer) ExtendedToolTipLookAndFeel.extendedToolTipDelayMs)
                        .toString());

        Element magicLenseLookAndFeel = getOrCreateElement(settings,
                "MagicLenseLookAndFeel");
        magicLenseLookAndFeel.setAttribute("enabled",
                ((Boolean) MagicLenseLookAndFeel.getInstance().isEnabled())
                        .toString());
        Element zoomFactorEl = getOrCreateElement(magicLenseLookAndFeel,
                "zoomFactor");
        zoomFactorEl.setText(((Double) MagicLenseLookAndFeel.getInstance().getZoomFactor())
                .toString());

        Element textToSpeechLookAndFeel = getOrCreateElement(settings,
                "TextToSpeechLookAndFeel");
        textToSpeechLookAndFeel.setAttribute("enabled",
                ((Boolean) TextToSpeechLookAndFeel.getInstance().isEnabled())
                        .toString());
        Element voiceEl = getOrCreateElement(textToSpeechLookAndFeel, "voice");
        voiceEl.setText(TextToSpeechLookAndFeel.getInstance().getVoiceString());
        FileOutputStream fos = null;
        XMLOutputter xmlOut = new XMLOutputter(Format.getPrettyFormat());
        try {
            fos = new FileOutputStream(
                    EasyAccessibilityLookAndFeel.configFilename);
            xmlOut.output(doc, fos);
        } catch (IOException e) {
            System.err
                    .println("Trying to save EasyAccessibilityLookAndFeel configuration to file: "
                            + EasyAccessibilityLookAndFeel.configFilename);
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                }
            }
        }

    }

    private static Element getOrCreateElement(Element parentElement,
            String elementName) {
        Element el = parentElement.getChild(elementName);
        if (el == null) {
            el = new Element(elementName);
            parentElement.addContent(el);
        }
        return el;
    }

    /**
     * Checks if this component is contained by an instance the specified class name
     * 
     * @param component
     * @param simpleClassName
     *            the non-fully qualified name (e.g., JLabel)
     * @return true if this component has any anscestor of this class name
     */
    public static boolean isContainedBy(JComponent component,
            String simpleClassName) {
        Container c = (Container) component;
        while (c != null) {
            if (c.getClass().getSimpleName().equals(simpleClassName)) {
                return true;
            }
            c = c.getParent();
        }
        return false;
    }

    /**
     * Checks if this component is contained by an instance of any of the specified class names
     * 
     * @param component
     * @param simpleClassNames
     *            the non-fully qualified names to check(e.g., JLabel)
     * @return true if this component has any of the class names as anscestors
     */
    public static boolean isContainedBy(JComponent component,
            String[] simpleClassNames) {
        Container c = (Container) component;
        while (c != null) {
            for (String simpleClassName : simpleClassNames) {
                if (c.getClass().getSimpleName().equals(simpleClassName)) {
                    return true;
                }
            }
            c = c.getParent();
        }
        return false;
    }

    /**
     * Get the absolute position of a component in relation to its RootPane
     * 
     * @param c
     * @return
     */
    public static Point getAbsolutePosition(JComponent c) {
        int x = 0;
        int y = 0;
        Component comp = c;
        while (!(comp instanceof JRootPane)) {
            x += comp.getX();
            y += comp.getY();
            comp = comp.getParent();
        }
        return new Point(x, y);
    }

    /**
     * Returns the index in the parent's component array that this component is
     * at.
     * 
     * @param c The component
     * @return the index
     */
    public static int getComponentIndex(JComponent c) {
        Component[] comps = c.getParent().getComponents();
        for (int i = 0; i < comps.length; i++) {
            if (comps[i] == c) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Returns whether or not the EasyAccessibilityLookAndFeel is enabled
     * @return true when enabled
     */
    public static boolean isEnabled() {
        return enabled;
    }

}
